#
# Sorto, the sorting game
# Copyright (C) 2012 Ralph Preston
# ralph.preston@gmail.com
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to:
#
# Free Software Foundation, Inc.
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301
# USA.
#
from sorto_board       import SortoBoard
from sorto_base_player import SortoBasePlayer

class SortoPlayer(SortoBasePlayer):
    """
    This class inherits from SortoBasePlayer which makes available a
    board and counters.
    
    self._board is the game board. Set by the controller at the start
    of every game. Initial value is [None]*36

    self._available_counters are the counters to place. Set by the
    controller during game play. Is a list of integers. Ex: [1,2,9]
    """
    def __init__(self, view=None):
        SortoBasePlayer.__init__(self, name='Player X', 
                                 seed=1, view=None, color='#ff0000')
        return

    def new_game(self, game_number):
        SortoBasePlayer.new_game(self, game_number)
        """
        The controller calls this at the start of every game. Use as
        you will.
        """
        return

    def game_over(self):
        SortoBasePlayer.game_over(self)
        """
        Yes, this is called when the game is over. When any player in
        the game satisfies the winning condition, or the round count
        reaches a threshold, this method is called on all players.
        """
        return

    def examine_table(self, players_boards, players_counters):
        """
        The player may view the table and examine other players boards
        and counters, in case this effects their decisions.

        players_boards is a dictionary using the players name as a key
        and a boards as the value. Ex: 
        { 
          'Player 1' : [None, None, 3, ... , None, None], 
          'Player 2' : [None, None, 3, ... , None, None], 
        }

        players_counters is a dictionary using the players name as a
        key and the value is a list of available counters.
        { 
          'Player 1' : [ 3, 5, 9 ], 
          'Player 2' : [ 3, 5, 9 ],
        }
        """
        return

    def play_roll(self, roll):
        """
        After receiving a game board and starting counters the
        controller begins generating a roll from 1 to 20.  The player
        must place/move one of their counters onto the board in the
        square rolled. If there are available counters the player must
        place one of them on the location rolled.

        The roll is an integer value from 1 to 20.

        Play the roll by modifying self._board.
        """
        if len(self._available_counters) > 0:
            self._play_new_counter(roll)
        else:
            self._move_counter(roll)
            pass
        return

    #################################################################
    #
    # All methods from here on down are just to support the sample
    # player and not part of the API, but some of them may be useful.
    #
    #################################################################
    
    def _play_new_counter(self, roll):
        # Find the number closest to the roll and place it.
        closest_distance = None
        closest = None

        for counter in self._available_counters:
            distance = abs(counter - roll)
            if closest_distance is None or distance < closest_distance:
                closest_distance = distance
                closest = counter
                pass
            pass
        # Place the closest value on the number
        (index_1, index_2) = self.convert_roll_to_board_index(roll)
        if closest < roll:
            # If the closest counter is less than the roll place it on
            # the lower of the two possible spaces on the board. If
            # that spot is taken place it on the upper space. If that
            # spot is also taken follow the chain on spaces descending
            # until a spot is found or we reach the lower side of the
            # board then follow the chain to the upper spaces.
            if self._board[index_1] == SortoBoard.EMPTY:
                self._board[index_1] = closest
            elif index_2 is not None and self._board[index_2] == SortoBoard.EMPTY:
                self._board[index_2] = closest
            else:
                placed_piece = False
                for lower_index in range(index_1, -1, -1):
                    if self._board[lower_index] == SortoBoard.EMPTY:
                        self._board[lower_index] = closest
                        placed_piece = True
                        break
                    pass
                if not placed_piece:
                    for upper_index in range(index_1, 36):
                        if self._board[upper_index] == SortoBoard.EMPTY:
                            self._board[upper_index] = closest
                            break
                        pass
                    pass
                pass
            pass
        else:
            if index_2 is not None and self._board[index_2] == SortoBoard.EMPTY:
                self._board[index_2] = closest
            elif self._board[index_1] == SortoBoard.EMPTY:
                self._board[index_1] = closest
            else:
                placed_piece = False
                for upper_index in range(index_1, 36):
                    if self._board[upper_index] == SortoBoard.EMPTY:
                        self._board[upper_index] = closest
                        placed_piece = True
                        break
                    pass
                if not placed_piece:
                    for lower_index in range(index_1, -1, -1):
                        if self._board[lower_index] == SortoBoard.EMPTY:
                            self._board[lower_index] = closest
                            break
                        pass
                    pass
                pass
            pass

        # Remove the counter from the list of available counters
        self._available_counters.remove(closest)
        
        return

    def _move_counter(self, roll):
        # Strategy is to divide the board into 12 equal
        # zones. Counter 1 goes in the first zone (indexs 0-2,
        # board spaces 1,2,3). Counter 2 goes in the second zone
        # (index 3-5, board spaces 3,4,4), etc.
        
        # Find the zones to play
        indexes = self._board.convert_roll_to_index(roll)
        counter_for_index = {}
        for ii in indexes:
            if ii is None: continue
            counter = self._counter_for_index(ii)
            counter_for_index[ii] = counter
            pass

        for index in counter_for_index:
            c = self._counter_for_index(index)
            if self._counter_in_zone(index): continue

            index_of_counter = self._board.index(c)
            self._board[index_of_counter] = SortoBoard.EMPTY
            legal_moves = self._board.legal_places_for_roll_and_counter(roll,c)
            self._board[index_of_counter] = c
            if legal_moves:
                move = self._best_move_for_counter(legal_moves, c)
                if move is not None:
                    self._board.remove_counter(c)
                    self._board[move] = c
                    pass
                return
            pass
        
        return

    def _best_move_for_counter(self, legal_moves, counter):
        zones = self._get_zones_for_counter(counter)
        for move in legal_moves:
            if move in zones:
                return move
            pass
        return None

    def _get_zones_for_counter(self, counter):
        start = (counter-1)*3
        zones = [start, start+1, start+2]
        return zones

    def _counter_in_zone(self, index):
        # Given an index, determine the three spaces for it's
        # zone. Return True if the counter is in that zone.
        for counter in range(1,13):
            zones = self._get_zones_for_counter(counter)
            if index in zones:
                for space in zones:
                    if self._board[space] == counter:
                        return True
                    pass
                pass
            pass        
        return False
    
    def _counter_for_index(self, index):
        # Determine which counter is supposed to be in the zone that
        # contains the specified index.  Ex: if the index is 33 the
        # counter for the zone containing index 33 is 12.
        for counter in range(1,13):
            zones = self._get_zones_for_counter(counter)
            if index in zones:
                return counter
            pass
        return None
    
    pass # End of class SortoPlayer

